class WxRequest {
    // 定义实例属性，用来设置默认请求参数
    defaults = {
        baseURL: '', // 请求基准地址
        url: '', // 接口的请求路径
        data: null, // 请求参数
        method: 'GET', // 默认的请求方法
        // 请求头
        header: {
            'Content-type': 'application/json' // 设置数据的交互格式
        },
        timeout: 60000, // 默认的超时时长，小程序默认的超时时长是 1 分钟
        isLoading: true // 控制是否使用默认的 loading，默认值是 true 表示使用默认的 loading
    }

    // 定义拦截器对象
    interceptors = {
        // 请求拦截器
        request: (config) => config,

        // 响应拦截器
        response: (response) => response
    }

    // 定义数组队列，用来存储请求队列、存储请求标识
    queue = []

    // constructor 用于创建和初始化类的属性以及方法
    constructor(params = {}) {
        // 注意：需要传入的参数，覆盖默认的参数，因此传入的参数需要放到最后
        this.defaults = Object.assign({}, this.defaults, params)
    }

    /**
     * @description request 实例方法发送网络请求，接收一个对象类型的参数
     * @param {*} options 属性值和 wx.request() 方法调用时传递的参数保持一致
     * @returns Promise
     */
    request(options) {
        // 如果有新的请求，就清除上一次的定时器
        this.timerId && clearTimeout(this.timerId)

        // 合并完整的请求地址
        options.url = this.defaults.baseURL + options.url

        // 合并请求参数：调用实例方法时传入的覆盖默认的以及实例配置的
        options = {...this.defaults, ...options}

        // 控制 loading 的显示与隐藏
        if (options.isLoading && options.method !== 'UPLOAD') {
            this.queue.length === 0 && wx.showLoading()
            this.queue.push('request')
        }

        // 在请求发送之前，调用请求拦截器，新增和修改请求参数
        // 请求拦截器内部，会将新增和修改以后的参数返回
        options = this.interceptors.request(options)

        // 需要使用 Promise 封装 wx.request，处理异步请求
        return new Promise((resolve, reject) => {
            // 如果 method 等于 UPLOAD 说明需要调用 wx.uploadFile() 方法
            // 否则调用的是 wx.request() 方法
            if (options.method === 'UPLOAD') {
                wx.uploadFile({
                    ...options,

                    success: (res) => {
                        // 需要将服务器返回的 JSON 字符串 通过 JSON.parse 转成对象
                        res.data = JSON.parse(res.data)

                        // 合并参数
                        const mergeRes = Object.assign({}, res, {
                            config: options,
                            isSuccess: true
                        })

                        resolve(this.interceptors.response(mergeRes))
                    },

                    fail: (err) => {
                        // 合并参数
                        const mergeErr = Object.assign({}, err, {
                            config: options,
                            isSuccess: false
                        })

                        reject(this.interceptors.response(mergeErr))
                    }
                })
            } else {
                wx.request({
                    ...options,

                    // 当接口调用成功时会触发 success 回调函数
                    success: (res) => {
                        // 合并请求参数，方便进行代码调试
                        // 追加 isSuccess 属性，是为了标识响应拦截器是 success 调用还是 fail 调用
                        const mergeRes = Object.assign({}, res, {
                            config: options,
                            isSuccess: true
                        })
                        resolve(this.interceptors.response(mergeRes))
                    },

                    // 当接口调用失败时会触发 fail 回调函数
                    fail: (err) => {
                        // 合并请求参数，方便进行代码调试
                        // 追加 isSuccess 属性，是为了标识响应拦截器是 success 调用还是 fail 调用
                        const mergeErr = Object.assign({}, err, {
                            config: options,
                            isSuccess: false
                        })
                        reject(this.interceptors.response(mergeErr))
                    },

                    // 接口调用结束的回调函数（调用成功、失败都会执行）
                    complete: () => {
                        // 如果需要显示 loading ，那么就需要控制 loading 的隐藏
                        if (options.isLoading) {
                            // 在每一个请求结束以后，都会执行 complete 回调函数
                            // 每次从 queue 队列中删除一个标识
                            this.queue.pop()

                            // 解决并发请求，loading 闪烁问题
                            this.queue.length === 0 && this.queue.push('request')

                            //解决并发请求，loading 闪烁问题
                            this.timerId = setTimeout(() => {
                                this.queue.pop()

                                this.queue.length === 0 && wx.hideLoading()

                                clearTimeout(this.timerId)
                            }, 1)
                        }
                    }
                })
            }
        })
    }

    /**
     * @description 封装 GET 实例方法
     * @param {*} url 请求地址
     * @param {*} data 请求参数
     * @param {*} config 其他请求配置项
     * @returns Promise
     */
    get(url, data = {}, config = {}) {
        // 需要调用 request 请求方法发送请求，只需要组织好参数，传递给 request 请求方法即可
        // 当调用 get 方法时，需要将 request 方法的返回值 return 出去
        return this.request(Object.assign({url, data, method: 'GET'}, config))
    }

    /**
     * @description 封装 DELETE 实例方法
     * @param {*} url 请求地址
     * @param {*} data 请求参数
     * @param {*} config 其他请求配置项
     * @returns Promise
     */
    delete(url, data = {}, config = {}) {
        return this.request(Object.assign({url, data, method: 'DELETE'}, config))
    }

    /**
     * @description 封装 POST 实例方法
     * @param {*} url 请求地址
     * @param {*} data 请求参数
     * @param {*} config 其他请求配置项
     * @returns Promise
     */
    post(url, data = {}, config = {}) {
        return this.request(Object.assign({url, data, method: 'POST'}, config))
    }

    /**
     * @description 封封装 PUT 实例方法
     * @param {*} url 请求地址
     * @param {*} data 请求参数
     * @param {*} config 其他请求配置项
     * @returns Promise
     */
    put(url, data = {}, config = {}) {
        return this.request(Object.assign({url, data, method: 'PUT'}, config))
    }

    /**
     * @description 封封装 PUT 实例方法
     * @param {*} url 请求地址
     * @param {*} data 请求参数
     * @param {*} config 其他请求配置项
     * @returns Promise
     */
    patch(url, data = {}, config = {}) {
        return this.request(Object.assign({url, data, method: 'PATCH'}, config))
    }

    /**
     * @description 处理并发请求
     * @param  {...promise} promise 传入的每一项需要是 Promise
     * @returns Promise
     */
    all(...promise) {
        // 那么展开运算符会将传入的参数转成数组
        return Promise.all(promise)
    }

    /**
     * @description upload 实例方法，用来对 wx.uploadFile 进行封装
     * @param {*} url 文件的上传地址、接口地址
     * @param {*} filePath 要上传的文件资源路径
     * @param {*} name 文件对应的 key
     * @param {*} config 其他配置项
     */
    upload(url, filePath, name = 'file', config = {}) {
        return this.request(
            Object.assign({url, filePath, name, method: 'UPLOAD'}, config)
        )
    }
}

export default WxRequest
